/*
 * Copyright (c) 1994, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.io;

import java.nio.channels.FileChannel;
import sun.nio.ch.FileChannelImpl;


/**
 * 文件输出流是用于将数据写入<code>File</code>或<code>FileDescriptor</code>的输出流。
 * 文件是否可用或可创建取决于底层平台。特别是某些平台仅允许一次由一个<tt>FileOutputStream</tt>
 * （或其他文件写入对象）打开文件进行写入。在这种情况下，如果涉及的文件已打开，此类中的构造方法将失败。
 *
 * <p><code>FileOutputStream</code>用于写入原始字节流，如图像数据。
 * 对于写入字符流，请考虑使用<code>FileWriter</code>。
 *
 * @author  Arthur van Hoff
 * @see     java.io.File
 * @see     java.io.FileDescriptor
 * @see     java.io.FileInputStream
 * @see     java.nio.file.Files#newOutputStream
 * @since   JDK1.0
 */
public
class FileOutputStream extends OutputStream
{
    /**
     * 系统相关的文件描述符。
     */
    private final FileDescriptor fd;

    /**
     * 如果文件以追加模式打开，则为true。
     */
    private final boolean append;

    /**
     * 关联的通道，延迟初始化。
     */
    private FileChannel channel;

    /**
     * 引用文件的路径
     * （如果流是用文件描述符创建的，则为null）
     */
    private final String path;

    private final Object closeLock = new Object();
    private volatile boolean closed = false;

    /**
     * 创建一个文件输出流以写入具有指定名称的文件。
     * 创建一个新的<code>FileDescriptor</code>对象来表示此文件连接。
     * <p>
     * 首先，如果存在安全管理器，则以<code>name</code>作为参数调用其
     * <code>checkWrite</code>方法。
     * <p>
     * 如果文件存在但是目录而不是常规文件，不存在但无法创建，
     * 或由于任何其他原因无法打开，则抛出<code>FileNotFoundException</code>。
     *
     * @param      name   系统相关的文件名
     * @exception  FileNotFoundException  如果文件存在但是目录而不是常规文件，
     *                   不存在但无法创建，或由于任何其他原因无法打开
     * @exception  SecurityException  如果存在安全管理器且其
     *               <code>checkWrite</code>方法拒绝对文件的写访问。
     * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
     */
    public FileOutputStream(String name) throws FileNotFoundException {
        this(name != null ? new File(name) : null, false);
    }

    /**
     * 创建一个文件输出流以写入具有指定名称的文件。如果第二个参数为<code>true</code>，
     * 则字节将写入文件的末尾而不是开头。
     * 创建一个新的<code>FileDescriptor</code>对象来表示此文件连接。
     * <p>
     * 首先，如果存在安全管理器，则以<code>name</code>作为参数调用其
     * <code>checkWrite</code>方法。
     * <p>
     * 如果文件存在但是目录而不是常规文件，不存在但无法创建，
     * 或由于任何其他原因无法打开，则抛出<code>FileNotFoundException</code>。
     *
     * @param     name        系统相关的文件名
     * @param     append      如果为<code>true</code>，则字节将写入文件的末尾而不是开头
     * @exception  FileNotFoundException  如果文件存在但是目录而不是常规文件，
     *                   不存在但无法创建，或由于任何其他原因无法打开。
     * @exception  SecurityException  如果存在安全管理器且其
     *               <code>checkWrite</code>方法拒绝对文件的写访问。
     * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
     * @since     JDK1.1
     */
    public FileOutputStream(String name, boolean append)
        throws FileNotFoundException
    {
        this(name != null ? new File(name) : null, append);
    }

    /**
     * 创建一个文件输出流以写入由指定<code>File</code>对象表示的文件。
     * 创建一个新的<code>FileDescriptor</code>对象来表示此文件连接。
     * <p>
     * 首先，如果存在安全管理器，则以<code>file</code>参数表示的路径
     * 作为参数调用其<code>checkWrite</code>方法。
     * <p>
     * 如果文件存在但是目录而不是常规文件，不存在但无法创建，
     * 或由于任何其他原因无法打开，则抛出<code>FileNotFoundException</code>。
     *
     * @param      file               要打开进行写入的文件。
     * @exception  FileNotFoundException  如果文件存在但是目录而不是常规文件，
     *                   不存在但无法创建，或由于任何其他原因无法打开
     * @exception  SecurityException  如果存在安全管理器且其
     *               <code>checkWrite</code>方法拒绝对文件的写访问。
     * @see        java.io.File#getPath()
     * @see        java.lang.SecurityException
     * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
     */
    public FileOutputStream(File file) throws FileNotFoundException {
        this(file, false);
    }

    /**
     * 创建一个文件输出流以写入由指定<code>File</code>对象表示的文件。
     * 如果第二个参数为<code>true</code>，则字节将写入文件的末尾而不是开头。
     * 创建一个新的<code>FileDescriptor</code>对象来表示此文件连接。
     * <p>
     * 首先，如果存在安全管理器，则以<code>file</code>参数表示的路径
     * 作为参数调用其<code>checkWrite</code>方法。
     * <p>
     * 如果文件存在但是目录而不是常规文件，不存在但无法创建，
     * 或由于任何其他原因无法打开，则抛出<code>FileNotFoundException</code>。
     *
     * @param      file               要打开进行写入的文件。
     * @param     append      如果为<code>true</code>，则字节将写入文件的末尾而不是开头
     * @exception  FileNotFoundException  如果文件存在但是目录而不是常规文件，
     *                   不存在但无法创建，或由于任何其他原因无法打开
     * @exception  SecurityException  如果存在安全管理器且其
     *               <code>checkWrite</code>方法拒绝对文件的写访问。
     * @see        java.io.File#getPath()
     * @see        java.lang.SecurityException
     * @see        java.lang.SecurityManager#checkWrite(java.lang.String)
     * @since 1.4
     */
    public FileOutputStream(File file, boolean append)
        throws FileNotFoundException
    {
        String name = (file != null ? file.getPath() : null);
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkWrite(name);
        }
        if (name == null) {
            throw new NullPointerException();
        }
        if (file.isInvalid()) {
            throw new FileNotFoundException("Invalid file path");
        }
        this.fd = new FileDescriptor();
        fd.attach(this);
        this.append = append;
        this.path = name;

        open(name, append);
    }

    /**
     * 创建一个文件输出流以写入指定的文件描述符，
     * 该描述符表示与文件系统中实际文件的现有连接。
     * <p>
     * 首先，如果存在安全管理器，则以文件描述符<code>fdObj</code>
     * 参数作为参数调用其<code>checkWrite</code>方法。
     * <p>
     * 如果<code>fdObj</code>为null，则抛出<code>NullPointerException</code>。
     * <p>
     * 如果<code>fdObj</code>是{@link java.io.FileDescriptor#valid() 无效的}，
     * 此构造函数不会抛出异常。但是，如果在结果流上调用方法尝试对流进行I/O，
     * 则会抛出<code>IOException</code>。
     *
     * @param      fdObj   要打开进行写入的文件描述符
     * @exception  SecurityException  如果存在安全管理器且其
     *               <code>checkWrite</code>方法拒绝对文件描述符的写访问
     * @see        java.lang.SecurityManager#checkWrite(java.io.FileDescriptor)
     */
    public FileOutputStream(FileDescriptor fdObj) {
        SecurityManager security = System.getSecurityManager();
        if (fdObj == null) {
            throw new NullPointerException();
        }
        if (security != null) {
            security.checkWrite(fdObj);
        }
        this.fd = fdObj;
        this.append = false;
        this.path = null;

        fd.attach(this);
    }

    /**
     * 打开具有指定名称的文件，用于覆盖或追加。
     * @param name 要打开的文件名
     * @param append 文件是否以追加模式打开
     */
    private native void open0(String name, boolean append)
        throws FileNotFoundException;

    // 包装本机调用以允许检测
    /**
     * 打开具有指定名称的文件，用于覆盖或追加。
     * @param name 要打开的文件名
     * @param append 文件是否以追加模式打开
     */
    private void open(String name, boolean append)
        throws FileNotFoundException {
        open0(name, append);
    }

    /**
     * 将指定字节写入此文件输出流。
     *
     * @param   b   要写入的字节。
     * @param   append   如果写操作首先将位置推进到文件末尾，则为{@code true}
     */
    private native void write(int b, boolean append) throws IOException;

    /**
     * 将指定字节写入此文件输出流。实现<code>OutputStream</code>的
     * <code>write</code>方法。
     *
     * @param      b   要写入的字节。
     * @exception  IOException  如果发生I/O错误。
     */
    public void write(int b) throws IOException {
        write(b, append);
    }

    /**
     * 将子数组作为字节序列写入。
     * @param b 要写入的数据
     * @param off 数据中的起始偏移量
     * @param len 写入的字节数
     * @param append {@code true}表示首先将位置推进到文件末尾
     * @exception IOException 如果发生I/O错误。
     */
    private native void writeBytes(byte b[], int off, int len, boolean append)
        throws IOException;

    /**
     * 将指定字节数组中的<code>b.length</code>字节写入此文件输出流。
     *
     * @param      b   数据。
     * @exception  IOException  如果发生I/O错误。
     */
    public void write(byte b[]) throws IOException {
        writeBytes(b, 0, b.length, append);
    }

    /**
     * 从指定字节数组开始于偏移量<code>off</code>处写入<code>len</code>字节
     * 到此文件输出流。
     *
     * @param      b     数据。
     * @param      off   数据中的起始偏移量。
     * @param      len   要写入的字节数。
     * @exception  IOException  如果发生I/O错误。
     */
    public void write(byte b[], int off, int len) throws IOException {
        writeBytes(b, off, len, append);
    }

    /**
     * 关闭此文件输出流并释放与此流关联的任何系统资源。
     * 此文件输出流可能不再用于写入字节。
     *
     * <p> 如果此流有关联的通道，则通道也会被关闭。
     *
     * @exception  IOException  如果发生I/O错误。
     *
     * @revised 1.4
     * @spec JSR-51
     */
    public void close() throws IOException {
        synchronized (closeLock) {
            if (closed) {
                return;
            }
            closed = true;
        }

        if (channel != null) {
            channel.close();
        }

        fd.closeAll(new Closeable() {
            public void close() throws IOException {
               close0();
           }
        });
    }

    /**
     * 返回与此流关联的文件描述符。
     *
     * @return  表示此<code>FileOutputStream</code>对象正在使用的
     *          文件系统中文件连接的<code>FileDescriptor</code>对象。
     *
     * @exception  IOException  如果发生I/O错误。
     * @see        java.io.FileDescriptor
     */
     public final FileDescriptor getFD()  throws IOException {
        if (fd != null) {
            return fd;
        }
        throw new IOException();
     }

    /**
     * 返回与此文件输出流关联的唯一{@link java.nio.channels.FileChannel FileChannel}
     * 对象。
     *
     * <p> 返回通道的初始{@link java.nio.channels.FileChannel#position()
     * 位置}将等于到目前为止写入文件的字节数，除非此流处于追加模式，
     * 在这种情况下它将等于文件的大小。向此流写入字节将相应地增加通道的位置。
     * 更改通道的位置，无论是显式地还是通过写入，都将更改此流的文件位置。
     *
     * @return  与此文件输出流关联的文件通道
     *
     * @since 1.4
     * @spec JSR-51
     */
    public FileChannel getChannel() {
        synchronized (this) {
            if (channel == null) {
                channel = FileChannelImpl.open(fd, path, false, true, append, this);
            }
            return channel;
        }
    }

    /**
     * 清理与文件的连接，并确保当不再有对此流的引用时
     * 调用此文件输出流的<code>close</code>方法。
     *
     * @exception  IOException  如果发生I/O错误。
     * @see        java.io.FileInputStream#close()
     */
    protected void finalize() throws IOException {
        if (fd != null) {
            if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
                flush();
            } else {
                /* 如果fd是共享的，FileDescriptor中的引用
                 * 将确保只有在安全的情况下才调用终结器。
                 * 使用fd的所有引用都已变得不可达。我们可以调用close()
                 */
                close();
            }
        }
    }

    private native void close0() throws IOException;

    private static native void initIDs();

    static {
        initIDs();
    }

}
